home *** CD-ROM | disk | FTP | other *** search
-
-
-
- 229
-
- CHAPTER 22 - BCD NUMBERS
-
-
- This chapter covers numbers which are in BCD format, both packed
- and unpacked. You will probably never need to write any programs
- on the 8086 that need these instructions, so you can either do
- this chapter because it is the only time that you will run into
- them, or you can skip the chapter because you will never see them
- again. My advice would be to go through it anyways so you know
- what the capabilities of the 8086 are. The programs in this
- chapter are more advanced so will be more of a challenge to your
- understanding.
-
- BCD stands for binary coded decimals. In their unpacked form,
- each byte stands for a single decimal digit. If we take a number
- like 831974 and put it in memory, the bytes will look like this:
-
- 08h
- 03h
- 01h
- 09h
- 07h
- 04h
-
- With high memory at the top and low memory at the bottom. Notice
- that the top number is not ASCII '8' (hex 38), but the number 8
- (hex 08). The same holds true for all the bytes; they are not
- ASCII characters, they are numbers.
-
- Why would we want to have numbers like this? They use up more
- space (about 2 1/2 times as much), and the arithmetic operations
- are slower (a multiplication on the 8086 can be several HUNDRED
- times slower). They are not used for scientific operations. They
- are used in business for billing. In a typical billing operation,
- the number is entered from a terminal and stored. At the end of
- the month, the number is printed on one line of the bill, and
- then added to the total. If it were converted to an integer, it
- would be necessary to do one conversion during data entry, then
- another conversion during printing. This way, the only time it
- will be converted is if it is used in some arithmetic.
-
- One excuse for using BCD numbers is that they are more accurate.
- It is true that they have no rounding errors, but neither do
- integers. Integers and BCD numbers can have the same accuracy.
-
- The typical form for BCD numbers is 18 digits. If you want to
- convert an 18 digit number to a standard integer, it takes about
- 25 multiplications. That is a lot of multiplications to convert
- one number. Similarly, it takes about 25 divisions to get a
- number out of integer form back into a BCD number. This is a big
- waste of time for a business situation. We keep the numbers in
- decimal form and use them occasionally for arithmetic.
-
- Actually, you would have to be crazy to use an 8086 for BCD
-
- ______________________
-
- The PC Assembler Tutor - Copyright (C) 1989 Chuck Nelson
-
-
-
-
- The PC Assembler Tutor 230
- ______________________
-
- numbers. If you have a PC and use BCD numbers habitually, an 8087
- coprocessor will work on BCD numbers at lightning speed. An
- investment of $175 will save you countless hours of grief. Also,
- it is next to impossible to do BCD division on the 8086, but
- takes no longer than normal to do BCD division on the 8087.
-
- That being said, just consider this chapter an academic exercise.
- It will give you the information so if the question should arise
- at a party, you will be able to say how BCD arithmetic works on
- the 8086.
-
- All 8086 numbers have the least significant digit in low memory
- and the most significant digit in high memory. This includes
- packed and unpacked BCD numbers. In the unpacked form, each byte
- represents a digit, and has a value from 0 to 9. Here are some
- numbers and their unpacked BCD encoding (in hex):
-
- 3986149 27 961728 74610
-
- 03h 02h 09h 07h
- 09h 07h 06h 04h
- 08h 01h 06h
- 06h 07h 01h
- 01h 02h 00h
- 04h 08h
- 09h
-
- This wastes a lot of space. Someone early on noticed that the
- upper half byte is completely unused. You can cut the space
- consumption in half if you put two digits in each byte - one in
- the lower half byte, and the other in the upper half byte. This
- packing always starts from the least significant digit and goes
- to the most significant digit. Here are the same numbers in
- packed form.
-
- 3986149 27 961728 74610
-
- 03h 27h 96h 07h
- 98h 17h 46h
- 61h 28h 10h
- 49h
-
- This is a considerable saving in space. The 8087 (not 8086)
- standard for these numbers is 10 byte long numbers. The low order
- 9 bytes contain 18 digits. The last byte is 00h if the number is
- positive and 80h if the number is negative. Here are an 18 digit
- positive number and an 18 digit negative number, along with their
- 10 byte BCD encoding.
-
-
-
-
-
-
-
-
-
-
-
-
-
- Chapter 22 - BCD Numbers 231
- ________________________
-
- +137486298374691552 -581726405829645298
-
- 00 80
- 13 58
- 74 17
- 86 26
- 29 40
- 83 58
- 74 29
- 69 64
- 15 52
- 52 98
-
-
- Let's work with the packed BCD numbers first.
-
-
- PACKED BCD NUMBERS
-
- The 8086 can manipulate packed BCD numbers. There is a subprogram
- in asmhelp.obj that gets a BCD number from the keyboard and one
- that prints a BCD number on the screen. Let's do some input and
- output first.
-
- ; - - - - - - - - - - START DATA BELOW THIS LINE
- variable1dw ? ; first four bcd digits
- variable2dw ? ; second four bcd digits
- variable3dw ? ; third four bcd digits
- variable4dw ? ; fourth four bcd digits
- variable5dw ? ; last two bcd digits and sign
- ; - - - - - - - - - - END DATA ABOVE THIS LINE
-
- ; - - - - - - - - - - START CODE BELOW THIS LINE
-
- outer_loop:
- lea ax, variable1
- call get_bcd
-
- mov ax, variable5
- call print_hex
- mov ax, variable4
- call print_hex
- mov ax, variable3
- call print_hex
- mov ax, variable2
- call print_hex
- mov ax, variable1
- call print_hex
-
- lea ax, variable1
- call print_bcd
- loop outer_loop
-
- ; - - - - - - - - - - END CODE ABOVE THIS LINE
-
- This gets a 10 byte BCD number, prints it out in hex (with high
- memory on top), and then prints the BCD number out again. Commas
-
-
-
-
- The PC Assembler Tutor 232
- ______________________
-
- are allowed. Printing the number in hex form allows you to see
- what the numbers look like internally.
-
- There are two instructions for BCD numbers - DAA (decimal adjust
- for addition), and DAS (decimal adjust for subtraction). We'll
- use each one on individual bytes to see how they work. Let's try
- DAA.
-
-
- ; - - - - - - - - - - START CODE BELOW THIS LINE
- mov ax_byte, 0C4h ; half registers, hex
- mov bx_byte, 094h ; half regs signed, hex
- mov dx_byte, 91h ; half registers, signed
- lea ax, ax_byte
- call set_reg_style
-
- sub cx, cx ; clear cx for clarity
- outer_loop:
- mov ax, 0 ; clear the registers
- mov bx, 0
- mov dx, 0
- call set_count
- call show_regs
-
- call get_hex_byte ; byte for al
- mov dx, ax ; copy to dx
- push ax ; save al
- call get_hex_byte ; byte for bl
- mov bl, al
- mov bh, bl ; copy to bh
- pop ax ; restore al
- call show_regs_and_wait
- add al, bl ; normal add
- mov dx, ax ; copy to dx
- call show_regs_and_wait
- daa ; make adjustment
- mov dx, ax ; copy to dx
- call show_regs_and_wait
- jmp outer_loop
-
- ; - - - - - - - - - - END CODE ABOVE THIS LINE
-
- Since the program works with both BCD adjustments and integer
- arithmetic, DX shows a signed integer copy of AX and BH shows a
- signed integer copy of BL. A copy of AX is moved to DX every time
- an operation is performed on AL.
-
- The idea of this subprogram is to enter hex numbers that look
- like decimal numbers - e.g. 65h, 78h, 08h, 29h. You can enter
- numbers that contain A-F if you want to see what happens with bad
- data. Each half byte of a BCD number should look like a decimal
- number. You will notice that the actual addition is done by ADD.
- The alteration is done by DAA, which assumes that you have just
- added two legitimate BCD numbers, and the result is in AL. The
- register MUST be AL. What DAA does will be discussed in a moment.
-
-
-
-
-
-
- Chapter 22 - BCD Numbers 233
- ________________________
-
- The DAA instruction looks at AL as being two half bytes. If the
- result from the lower half byte addition is 10 or over, the 8086
- subtracts 10 and then adds 1 to the upper half byte.{1} For
- instance, if the result is 17, it subtracts 10, to leave 7 in the
- lower half byte, and adds 1 to the upper half byte. After it has
- done this, it looks at the upper half byte. If the upper half
- byte is now 10 or over, it subtracts 10 and sets the carry flag
- for future additions.{2}
-
- While the whole thing sounds a little confusing, if you look at
- the bytes in hex, they will act in the same way as if you were
- doing normal decimal addition with pencil and paper except all
- the carries are done at one time. CF will be set if the hundreds
- digit is 1 and will be cleared if the hundreds digit is 0. Use
- the previous program a few times to get the hang of what is going
- on. When you feel confident, we'll move on to subtraction.
-
-
- SUBTRACTION
-
- DAS (decimal adjust for subtraction) is similar to DAA. It makes
- an adjustment after the subtraction itself. We'll use the same
- program as before, making two alterations. Where you have ADD,
- change it to SUB ; where you have DAA, change it to DAS. That's
- all.
-
- add -> sub
- daa -> das
-
- Run the program, and put in a number of examples. If the lower
- number is larger than the top number, there will be a borrow.
-
- DAS works the opposite of DAA. If there has been a borrow into
- the low half byte, it adds 10 to the low half byte and subtracts
- 1 from the high half byte. It then looks at the high half byte.
- ____________________
-
- 1. The technical description is a little confusing, so if you
- get muddled up, just forget it. Here it goes. (AF is the
- auxilillary flag). The 8086 checks for either (1) the low half
- byte > 9 (that is, between 10 and 15) or (2) AF = 1. AF is set on
- a byte addition when there is a carry out of the low half byte,
- that is, the result is greater than 16 for the low half byte. If
- either of these events has occured, the 8086 ADDS 6 to the low
- half byte. Why? Let 10 + x represent the result of the addition,
- where x < 10. We then have:
-
- 10 + x + 6 = 10 + 6 + x = 16 + x
-
- but 16 is 0001 0000 (10h), the first bit in the high byte, so
- this has the effect of leaving the part less than 10 in the low
- half byte and adding 1 (the carry) to the high half byte.
-
- 2. This is exactly the same logic as in the last footnote,
- except that the 8086 adds 60h to the high byte. This has the
- effect of shifting the excess out of AL altogether. The 8086 then
- sets the carry flag.
-
-
-
-
- The PC Assembler Tutor 234
- ______________________
-
- If there has been a borrow into it, the 8086 adds 10 to the high
- half byte and sets the carry flag.
-
- Do a few more examples with the program to make sure you
- understand what's happening. It will look just like what you do
- with pencil and paper, except that with pencil and paper you do
- the borrow before you do the subtraction and here the borrow is
- done afterwards if it is needed.
-
-
- ADDITION AND SUBTRACTION
-
- We have a number of possibilities with addition and subtraction.
- Here they are. The sign of the numbers is in parentheses and the
- operation is in between.
-
- (+) + (+) (+) + (-) (+) - (+) (+) - (-)
- (-) + (+) (-) + (-) (-) - (+) (-) - (-)
-
- The subroutines we use are going to assume that (1) both numbers
- are the same sign, and (2) for subtraction, the larger number is
- on top and the smaller number is on the bottom. There should be a
- section of the program that decides whether addition or
- subtraction should be used, and which number is on top. Then
- comes the addition or subtraction. We will write the first part
- later. For now, when you input numbers, both numbers must have
- the same sign, and for subtraction, the larger number must be
- first. Here's the addition subroutine.
-
- ; - - - - - START SUBROUTINE BELOW THIS LINE
- _bcd_addition proc near
-
- RESULT_ADDRESS EQU [bp + 8]
- BOTTOM_ADDRESS EQU [bp + 6]
- TOP_ADDRESS EQU [bp + 4]
-
- push bp
- mov bp, sp
- PUSHREGS ax, bx, cx, si, di
-
- mov si, TOP_ADDRESS
- mov bx, BOTTOM_ADDRESS
- mov di, RESULT_ADDRESS
- mov cx, 9 ; 9 bytes of BCD numbers
- clc ; clear the carry flag
-
- add_loop:
- mov al, [si] ; move and add bytes
- adc al, [bx] ; add two numbers and the carry
- daa ; bcd adjust
- mov [di], al ; store result
- inc si ; increment pointers
- inc bx
- inc di
- loop add_loop
-
- jnc continue_addition ; BCD overflow if CF = 1
-
-
-
-
- Chapter 22 - BCD Numbers 235
- ________________________
-
- lea ax, overflow_message
- call print_string
-
- continue_addition:
- mov al, [si] ; sign of top addend to result
- mov [di], al
-
- POPREGS ax, bx, cx, si, di
- pop bp
- ret
-
- _bcd_addition endp
- ; - - - - END SUBROUTINE ABOVE THIS LINE
-
- AL contains the first number. We use the carry flag (ADC) for
- carrying from byte to byte. The carry flag is cleared before the
- first addition since we don't want a carry there. The INC
- instruction was designed by Intel so that it would not alter the
- carry flag just so we could use it in situations like this.
-
- If CF = 1 upon exiting the loop, the result was too large and we
- had overflow. In this case we print a message to that effect.
-
- The result [di] can be stored in the same place as one of the
- numbers. The addition of each byte is completed before the result
- for that byte is stored, so this won't interfere with the
- addition. We assume that the sign of the result is the sign of
- the top addend. (If this goes into a working program, it will
- only be used if both numbers have the same sign).
-
- This is designed as a C subroutine. The calling program pushes
- things on the stack and it has the responsibility of popping them
- off the stack on return from the call.
-
- Here's the calling routine:
-
- ; - - - - - ENTER DATA BELOW THIS LINE
- bcd_num1 dt ?
- bcd_num2 dt ?
- bcd_num3 dt ?
- overflow_message db "We had overflow.", 0
- ; - - - - - ENTER DATA ABOVE THIS LINE
- ; - - - - - ENTER CODE BELOW THIS LINE
-
- outer_loop:
- lea ax, bcd_num1 ; get 2 numbers for addition
- call get_bcd
- lea ax, bcd_num2
- call get_bcd
-
- lea ax, bcd_num3 ; push addresses for subroutine
- push ax
- lea ax, bcd_num2
- push ax
- lea ax, bcd_num1
- push ax
-
-
-
-
-
- The PC Assembler Tutor 236
- ______________________
-
- call _bcd_addition
- add sp, 6 ; 3 pushes = 6 bytes
-
- lea ax, bcd_num1 ; print both numbers and result
- call print_bcd
- lea ax, bcd_num2
- call print_bcd
- lea ax, bcd_num3
- call print_bcd
- loop outer_loop
-
- ; - - - - - ENTER CODE ABOVE THIS LINE
-
- This is the main program. The other one should be in the
- subroutine section. This program is straightforward. We get two
- numbers, call the subroutine, and print the numbers and the
- result. Try some numbers. The results you get will always have
- the sign of the top number and will have the same numerical
- result as adding two unsigned numbers.
-
- The subtraction routine is the same as the addition but we need
- to make some changes because it is subtraction:
-
- ADC -> SBB
- DAA -> DAS (decimal adjust for subtraction)
-
- add_loop: -> subtract_loop
- loop add_loop -> loop subtract_loop
-
- jmp continue_addition -> jmp continue_subtraction
- continue_addition: -> continue_subtraction:
-
- Also, we want to change the subroutine name and call
-
- _bcd_addition -> _bcd_subtraction
- call _bcd_addition -> call _bcd_subtraction
-
- Do all these changes, run the program, but make sure the top
- number is larger or you will get strange results.
-
- We now have an addition routine that only works with the right
- numbers and a subtraction routine that is very touchy about the
- numbers that are put in. How can these things help us? In fact
- they are almost all we need. The only thing else we need is a
- preliminary subroutine to organize what we do. To see why we have
- some orginizational problems, take out a pencil and paper and do
- the following additions and subtractions. Do these with a pencil
- and paper, not a pocket calculator:
-
- (+15) + (+27) (+15) + (-27)
- (+15) - (+27) (+15) - (-27)
- (-15) + (+27) (-15) + (-27)
- (-15) - (+27) (-15) - (-27)
- (+27) + (+15) (+27) + (-15)
- (+27) - (+15) (+27) - (-15)
- (-27) + (+15) (-27) + (-15)
- (-27) - (+15) (-27) - (-15)
-
-
-
-
- Chapter 22 - BCD Numbers 237
- ________________________
-
-
- There are only four possible answers: +12, -12, +42 and -42. We
- had 16 different additions and subtractions, yet we got only 4
- possible answers and only 2 possible absolute values. We could
- have a different subroutine for each one, but 16 subprograms is a
- LOT of code, so it's easier to do it with 2 subprograms. We
- merely need to order the numbers and pick the correct subroutine.
-
- Here is the BCD driving routine. We'll get a number and then
- we'll get an operation, either addition or subtraction. If it is
- subtraction, when we get the second number, we REVERSE the sign.
- We don't even check what it is; plus becomes minus and minus
- becomes plus. What we have now is the ADDITION of two signed
- numbers. We XOR the two signs. If the result is 0, they both are
- the same sign and we can go to the addition subroutine. If the
- signs are different we need to subtract the smaller from the
- larger. We find out which one is larger and then call the
- subtraction routine. It is going to take you a little time to
- read this code.
-
- ; - - - - - START DATA BELOW THIS LINE
- top_number dt ?
- bottom_number dt ?
- result dt ?
- sign_mask db ?
- sign_message db "Enter either + or -.", 0
- overflow_message db "We had overflow.", 0
- ; - - - - - END DATA ABOVE THIS LINE
-
- ; - - - - - START CODE BELOW THIS LINE
- outer_loop:
- lea ax, top_number
- call get_bcd
-
- sign_loop:
- ; get either a '+' or a '-'
- lea ax, sign_message ; prompt for operation
- call print_string
- call get_ascii_byte ; operation type in al
- cmp al, '+'
- jne check_for_minus
- mov sign_mask, 00h ; for XOR of bottom number sign
- jmp get_second_number
- check_for_minus:
- cmp al, '-'
- jne sign_loop ; if not a minus then redo
- mov sign_mask, 80h ; for XOR of bottom sign
-
- get_second_number:
- lea ax, bottom_number
- call get_bcd
- ; XOR bottom sign with sign mask
- mov al, sign_mask
- xor BYTE PTR (bottom_number + 9), al ; sign byte
-
- ; same sign or different signs?
- mov ah, BYTE PTR (top_number + 9) ; sign byte
-
-
-
-
- The PC Assembler Tutor 238
- ______________________
-
- xor ah, BYTE PTR (bottom_number + 9) ; different?
- jnz which_is_larger ; if different, subtract
-
- ; same sign, so add
- lea ax, result ; push parameters and add
- push ax
- lea ax, bottom_number
- push ax
- lea ax, top_number
- push ax
- call _bcd_addition
- add sp, 6 ; adjust the stack
- jmp print_the_numbers
-
- which_is_larger:
- lea si, top_number + 8 ; top digits
- lea di, bottom_number + 8
- mov cx, 9 ; 9 bytes of digits
- check_for_greater_loop:
- mov al, [si] ; top number
- cmp al, [di] ; bottom number
- ja top_is_more
- jb bottom_is_more
- dec si ; equal, so continue
- dec di
- loop check_for_greater_loop
-
- ; we fell through so they are the same
- ; leave the top number on top
- top_is_more:
- lea ax, result
- push ax
- lea ax, bottom_number
- push ax
- lea ax, top_number
- push ax
- call _bcd_subtraction
- add sp, 6 ; adjust the stack
- jmp print_the_numbers
-
- bottom_is_more:
- lea ax, result
- push ax
- lea ax, top_number
- push ax
- lea ax, bottom_number
- push ax
- call _bcd_subtraction
- add sp, 6 ; adjust the stack
-
- print_the_numbers:
- lea ax, top_number
- call print_bcd
- lea ax, bottom_number
- call print_bcd
- lea ax, result
- call print_bcd
-
-
-
-
- Chapter 22 - BCD Numbers 239
- ________________________
-
- jmp outer_loop
- ; - - - - - END CODE ABOVE THIS LINE
-
- The sign_mask is either 00h if it is addition or 80h if it is
- subtraction. 00h XOR sign_byte will leave the sign byte
- unchanged. 80h XOR sign_byte will reverse the sign of the sign
- byte.
-
- Thus, as we did in the earlier multiplication and division
- routines, we sometimes change the sign of a number
- (bottom_number). In a real routine we would either have to make a
- copy of it when we change the sign or change the sign back at the
- end. Here we leave it with the sign change (if any) so you can
- see what is happening internally. If you want to restore the
- number, you can alter the code a little:
-
- print_the_numbers:
- mov al, sign_mask ; add this
- xor BYTE PTR (bottom_number + 9), al ; add this
- lea ax, top_number
-
- This will restore the bottom number to its original form ASSUMING
- THAT THE RESULT HAS NOT BEEN STORED THERE.
-
- It is possible to get -0 as a result. This is a legally defined
- number: +0 = -0.
-
- There are three places where we have 'BYTE PTR'. This is because
- the variables are defined as 10 byte long objects and the
- assembler will complain if we don't put in the 'BYTE PTR'.
-
- Finally, if we want to use the typical 'destination, source'
- style for the 8086, it is built in. Here it is for addition:
-
- lea ax, top_number
- push ax
- lea ax, bottom_number
- push ax
- lea ax, top_number
- push ax
-
- We put the address of 'top_number' where the result address goes.
- The routines store a byte only after all calculations are done on
- that particular byte, so there is no interference from doing
- this.
-
-